package com.ibm.cps.sql.expression;

import java.io.IOException;
import java.util.HashSet;
import java.util.Set;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializable;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.google.gson.Gson;
import com.ibm.cps.sql.expression.json.ComparisonExpressionDeserializer;
import com.ibm.util.ErrorCode;
import com.ibm.util.exception.CPSException;

@JsonDeserialize(using = ComparisonExpressionDeserializer.class)
public class ComparisonExpression implements BooleanExpression, RecursiveExpression, JsonSerializable{
	private Operator operator;
	private ValueExpression leftOperand;
	private ValueExpression rightOperand;
	
	public ComparisonExpression() {
		//Only used for serialization
	}

	public ComparisonExpression(Operator operator, ValueExpression leftOperand,
			ValueExpression rightOperand) {
		this.operator = operator;
		this.leftOperand = leftOperand;
		this.rightOperand = rightOperand;
	}

	@Override
	public String toString() {
		return "{\"ComparisonExpression\":{\"operator\":\"" + operator
				+ "\", \"leftOperand\":\"" + leftOperand
				+ "\", \"rightOperand\":\"" + rightOperand + "\"}}";
	}

	public static enum Operator{
		NOT_EQUALS("!="), EQUALS("="), GREATER_THAN(">"), GREATER_THAN_EQUALS(">="), LESS_THAN("<"), LESS_THAN_EQUALS("<=");
		
		private final String symbol;

		private Operator(String symbol) {
			this.symbol = symbol;
		}

		public String getSymbol() {
			return symbol;
		}
		
		public static Operator parse(String operator) throws CPSException {
			if (operator.equals("!="))
				return NOT_EQUALS;
			if (operator.equals("="))
				return EQUALS;
			if (operator.equals(">"))
				return GREATER_THAN;
			if (operator.equals(">="))
				return GREATER_THAN_EQUALS;
			if (operator.equals("<"))
				return LESS_THAN;
			if (operator.equals("<="))
				return LESS_THAN_EQUALS;
			throw new CPSException(ErrorCode.INVALID_INPUT_ERROR,
					"Invalid comparison operator " + operator
							+ " in expression");
		}

		public static boolean isValidOperator(String operator) {
			try {
				parse(operator);
				return true;
			} catch (CPSException ex) {
				return false;
			}
		}
	}

	@Override
	public boolean evaluate(FieldEvaluator evaluator) throws CPSException {
		Object left = leftOperand.evaluate(evaluator);
		Object right = rightOperand.evaluate(evaluator);
		try{
			if(!(left instanceof Comparable) || !(right instanceof Comparable))
				throw new CPSException(ErrorCode.INVALID_INPUT_ERROR, "Cannot compare between " + left.getClass().getName() + " and " + right.getClass().getName());
			// TODO : support Integer and Double compare.
			
			int comparison = ((Comparable)left).compareTo((Comparable)right);
			switch(operator){
			case NOT_EQUALS:
				return comparison != 0;
			case EQUALS:
				return comparison == 0;
			case GREATER_THAN:
				return comparison > 0;
			case GREATER_THAN_EQUALS:
				return comparison >= 0;
			case LESS_THAN:
				return comparison < 0;
			default:
				return comparison <= 0;
			}
		}catch(Exception comparisonException){
			throw new CPSException(ErrorCode.INVALID_INPUT_ERROR, "failed to compare between " + left.getClass() + " and " + right.getClass() + " in ComparisonExpression " + toString(), comparisonException);
		}
	}

	@Override
	public Set<String> collectVariableNames() {
		Set<String> names = new HashSet<>();
		names.addAll(leftOperand.collectVariableNames());
		names.addAll(rightOperand.collectVariableNames());
		return names;
	}

	@Override
	public ExpressionNode[] getChildNodes() {
		return new ExpressionNode[]{leftOperand, rightOperand};
	}

	@Override
	public void updateVariableName(String original, String fullQualifiedName) {
		leftOperand.updateVariableName(original, fullQualifiedName);
		rightOperand.updateVariableName(original, fullQualifiedName);
	}

	public Operator getOperator() {
		return operator;
	}

	public void setOperator(Operator operator) {
		this.operator = operator;
	}

	public ValueExpression getLeftOperand() {
		return leftOperand;
	}

	public void setLeftOperand(ValueExpression leftOperand) {
		this.leftOperand = leftOperand;
	}

	public ValueExpression getRightOperand() {
		return rightOperand;
	}

	public void setRightOperand(ValueExpression rightOperand) {
		this.rightOperand = rightOperand;
	}

	@Override
	public void serialize(JsonGenerator jgen, SerializerProvider provider)
			throws IOException, JsonProcessingException {
		jgen.writeStartObject();
		jgen.writeStringField("operator", operator.toString());
		jgen.writeStringField("leftOperandType", leftOperand.getClass().getName());
		jgen.writeObjectField("leftOperand", leftOperand);
		jgen.writeStringField("rightOperandType", rightOperand.getClass().getName());
		jgen.writeObjectField("rightOperand", rightOperand);
		jgen.writeEndObject();
	}

	@Override
	public void serializeWithType(JsonGenerator jgen,
			SerializerProvider provider, TypeSerializer typeSer)
			throws IOException, JsonProcessingException {
	};
	
	public static void main(String[] args) {
//		ComparisonExpression exp = new ComparisonExpression(ComparisonExpression.Operator.GREATER_THAN, new StringLiteralNode("input"), new NumberNode(2.3));
//		System.out.println(new Gson().toJson(exp));
	}
	
}
